코인 노래방을 한번씩 이용하는 편이다. 제목이 애매하게 기억이 안날 때 초성으로 검색하면 빠르게 검색할 수 있다. 초성검색 기술적으로 어떻게 가능한 것일까?
원리
어려울 것 같지만, 생각보다 간단하다. “보고싶다”를 검색한다고 가정했을 때 “ㅂㅗㄱㅗㅅㅣㅍㄷㅏ”와 같은 형태로 만들고, 리스트에 있는 항목들 또한 초/중/종성을 분리해서 포함되는지 확인 해주면 된다.
초/중/종성 분리하기
그렇다면 초/중/종성을 어떻게 분리하는걸까?
// 한글의 초성, 중성, 종성을 배열로 선언합니다.
const CHO = [
'ㄱ',
'ㄲ',
'ㄴ',
'ㄷ',
'ㄸ',
'ㄹ',
'ㅁ',
'ㅂ',
'ㅃ',
'ㅅ',
'ㅆ',
'ㅇ',
'ㅈ',
'ㅉ',
'ㅊ',
'ㅋ',
'ㅌ',
'ㅍ',
'ㅎ',
];
const JUNG = [
'ㅏ',
'ㅐ',
'ㅑ',
'ㅒ',
'ㅓ',
'ㅔ',
'ㅕ',
'ㅖ',
'ㅗ',
'ㅘ',
'ㅙ',
'ㅚ',
'ㅛ',
'ㅜ',
'ㅝ',
'ㅞ',
'ㅟ',
'ㅠ',
'ㅡ',
'ㅢ',
'ㅣ',
];
const JONG = [
'',
'ㄱ',
'ㄲ',
'ㄳ',
'ㄴ',
'ㄵ',
'ㄶ',
'ㄷ',
'ㄹ',
'ㄺ',
'ㄻ',
'ㄼ',
'ㄽ',
'ㄾ',
'ㄿ',
'ㅀ',
'ㅁ',
'ㅂ',
'ㅄ',
'ㅅ',
'ㅆ',
'ㅇ',
'ㅈ',
'ㅊ',
'ㅋ',
'ㅌ',
'ㅍ',
'ㅎ',
];
export function disassembleHangul(hangul: string) {
const unicode = hangul.charCodeAt(0);
if (unicode < 44032 || unicode > 55203) return hangul; // 한글이 아니면 그대로 반환
const cho = Math.floor((unicode - 44032) / 588);
const jung = Math.floor((unicode - 44032 - cho * 588) / 28);
const jong = (unicode - 44032) % 28;
return CHO[cho] + JUNG[jung] + JONG[jong];
}
- hangul 파라미터에는 초/중/종성으로 이루어진 한글 1자 혹은 영어 1자가 들어올 수 있다.
- charCodeAt(0)으로 유니코드로 치환한다.
- 유니코드가 알파벳 범주에 해당하면 초/중/종성으로 분리할 필요가 없기 때문에 그대로 반환한다.
- 초/중/종성의 유니코드를 찾아내고 각 유니코드를 다시 한글로 변환하면 끝!
어떻게 사용할까?
- 검색하려는 문자열들을 분리해서 초/중/종성으로 분리해내고 join을 통해 하나의 문자열로 합친다.
- toLowerCase가 있는 이유는 영문일 때는 소문자로 바꿔줘서 검색하기 위함이다.
- replaceAll 또한 좀 더 편리한 검색을 위해서 공백을 제거하고, 기존 리스트에 있는지 찾는다.
const disassembledSearchText = [...searchText]
.map(disassembleHangul)
.join("")
.toLowerCase()
.replaceAll(" ", "");
어디에 적용했는가?
인사담당자들이 지원자를 평가하는 “평가자 서비스”를 리뉴얼 할 때 “공고 검색”에 적용했다.
적용한 이유는 공고 제목 특성상 정확히 검색하기 어렵기 때문이다. 그런데 공고는 수십, 수백개가 쌓여있다. 검색하기가 어려우니까, 스크롤해서 찾는데 다 비슷한 공고명으로 인해서 스크롤도 쉽지 않다.
그래서, 많은 시간이 지났을 때 공고가 많이 쌓이더라도 어떻게 인사담당자들이 편리하게 검색할 수 있을지를 고민했다. 그 결과, 초성으로도 검색하게 해주고 띄어쓰기도 무시하면서 검색할 수 있게 해주면 편리할 것 같아서 개선을 진행했고, 사용성이 많이 개선됐다.
결론
생각보다 간단하게 개선할 수 있었다. 하지만, 이런 디테일까지 생각하기가 어렵기 때문에 적용되어 있지 않은 서비스가 많다. 검색을 많이 이용해야 하는 서비스 일 경우, 이런 사소하지만 신경을 써주는 디테일들이 사용자한테는 감동으로 다가오기 때문에 내가 개발하고 있는 서비스에 적용한번 해보자.